/*
 * brin_inclusion.c
 *		BRIN 包含操作类的实现
 *
 * 本模块为“包含”操作类提供 BRIN 支持函数的框架。每个操作类还需要一些 SQL
 * 级支持函数。
 *
 * “包含” BRIN 策略对于支持 R-Tree 操作的类型非常有用。该实现将这些操作直接映射到 BRIN 的
 * 块范围性质，存在两个例外：(a) 我们明确支持“空”的元素：至少对于范围类型，我们需要将
 * 空性与常规 R-Tree 策略分开考虑；以及 (b) 我们需要考虑“不可合并”的元素，换言之，一组
 * 元素的并集没有代表存在。至今为止，只有 INET 类型在这种情况下，即 IPv6 值无法与 IPv4
 * 值合并。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/brin/brin_inclusion.c
 */
#include "postgres.h"

#include "access/brin_internal.h"
#include "access/brin_tuple.h"
#include "access/genam.h"
#include "access/skey.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


/*
 * 额外的 SQL 层支持函数
 *
 * 过程编号不得使用保留给 BRIN 本身的值；请参见
 * brin_internal.h。
 */
#define		INCLUSION_MAX_PROCNUMS	4	/* 我们需要的最大支持过程 */
#define		PROCNUM_MERGE			11	/* 要求 */
#define		PROCNUM_MERGEABLE		12	/* 可选 */
#define		PROCNUM_CONTAINS		13	/* 可选 */
#define		PROCNUM_EMPTY			14	/* 可选 */


/*
 * 从 procnum 中减去此值以获得 InclusionOpaque 数组中的索引
 * （必须等于私有 procnums 的最小值）。
 */
#define		PROCNUM_BASE			11

/*-
 * 存储在 bv_values 数组中的值对应于：
 *
 * INCLUSION_UNION
 *		块范围内值的并集
 * INCLUSION_UNMERGEABLE
 *		块范围内的值是否无法合并
 *		（例如，IPv4 地址中的 IPv6 地址）
 * INCLUSION_CONTAINS_EMPTY
 *		块范围内的任何元组中是否存在空值
 */
#define INCLUSION_UNION				0
#define INCLUSION_UNMERGEABLE		1
#define INCLUSION_CONTAINS_EMPTY	2


typedef struct InclusionOpaque
{
	FmgrInfo	extra_procinfos[INCLUSION_MAX_PROCNUMS];
	bool		extra_proc_missing[INCLUSION_MAX_PROCNUMS];
	Oid			cached_subtype;
	FmgrInfo	strategy_procinfos[RTMaxStrategyNumber];
} InclusionOpaque;

static FmgrInfo *fc_inclusion_get_procinfo(BrinDesc *fc_bdesc, uint16 fc_attno,
										uint16 fc_procnum);
static FmgrInfo *fc_inclusion_get_strategy_procinfo(BrinDesc *fc_bdesc, uint16 fc_attno,
												 Oid fc_subtype, uint16 fc_strategynum);


/*
 * BRIN 包含 OpcInfo 函数
 */
Datum brin_inclusion_opcinfo(PG_FUNCTION_ARGS)
{
	Oid			fc_typoid = PG_GETARG_OID(0);
	BrinOpcInfo *fc_result;
	TypeCacheEntry *fc_bool_typcache = lookup_type_cache(BOOLOID, 0);

	/*
	 * opaque 的所有成员都是懒初始化的；两个 procinfo 数组
	 * 通过将 fn_oid 设置为 InvalidOid 开始为未初始化状态，
	 * 并且通过将 "missing" 设置为 false 来归零。在 cached_subtype
	 * 更改时，strategy_procinfos 元素可以通过将 fn_oid 归零
	 * 来使其无效。extra_procinfo 条目永远不会失效，但如果查找失败
	 * （这是预期的），则 extra_proc_missing 被设置为 true，
	 * 表示不再查找。
	 */
	fc_result = palloc0(MAXALIGN(SizeofBrinOpcInfo(3)) + sizeof(InclusionOpaque));
	fc_result->oi_nstored = 3;
	fc_result->oi_regular_nulls = true;
	fc_result->oi_opaque = (InclusionOpaque *)
		MAXALIGN((char *) fc_result + SizeofBrinOpcInfo(3));

	/* 并集 */
	fc_result->oi_typcache[INCLUSION_UNION] =
		lookup_type_cache(fc_typoid, 0);

	/* 包含不可合并的元素 */
	fc_result->oi_typcache[INCLUSION_UNMERGEABLE] = fc_bool_typcache;

	/* 包含空元素 */
	fc_result->oi_typcache[INCLUSION_CONTAINS_EMPTY] = fc_bool_typcache;

	PG_RETURN_POINTER(fc_result);
}

/*
 * BRIN 包含添加值函数
 *
 * 通过将给定的索引元组（其中包含某一页面范围的部分状态）
 * 与来自另一堆元组的给定值进行比较，来检查给定的索引元组。
 * 如果新值在已存在元组值指定的并集之外，则更新索引元组并返回 true。
 * 否则，返回 false，并在这种情况下不进行修改。
 */
Datum brin_inclusion_add_value(PG_FUNCTION_ARGS)
{
	BrinDesc   *fc_bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
	BrinValues *fc_column = (BrinValues *) PG_GETARG_POINTER(1);
	Datum		fc_newval = PG_GETARG_DATUM(2);
	bool		fc_isnull PG_USED_FOR_ASSERTS_ONLY = PG_GETARG_BOOL(3);
	Oid			fc_colloid = PG_GET_COLLATION();
	FmgrInfo   *fc_finfo;
	Datum		fc_result;
	bool		fc_new = false;
	AttrNumber	fc_attno;
	Form_pg_attribute fc_attr;

	Assert(!fc_isnull);

	fc_attno = fc_column->bv_attno;
	fc_attr = TupleDescAttr(fc_bdesc->bd_tupdesc, fc_attno - 1);

	/*
	 * 如果记录的值为 null，复制新值（我们知道不是 null），
	 * 我们几乎完成了。
	 */
	if (fc_column->bv_allnulls)
	{
		fc_column->bv_values[INCLUSION_UNION] =
			datumCopy(fc_newval, fc_attr->attbyval, fc_attr->attlen);
		fc_column->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(false);
		fc_column->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(false);
		fc_column->bv_allnulls = false;
		fc_new = true;
	}

	/*
	 * 如果块范围标记为包含不可合并的值，则无需进一步处理。
	 */
	if (DatumGetBool(fc_column->bv_values[INCLUSION_UNMERGEABLE]))
		PG_RETURN_BOOL(false);

	/*
	 * 如果 opclass 支持空值的概念，测试传递的新值是否为空；
	 * 如果返回 true，我们需要在元素中设置 "contains empty" 标志
	 * （除非已设置）。
	 */
	fc_finfo = fc_inclusion_get_procinfo(fc_bdesc, fc_attno, PROCNUM_EMPTY);
	if (fc_finfo != NULL && DatumGetBool(FunctionCall1Coll(fc_finfo, fc_colloid, fc_newval)))
	{
		if (!DatumGetBool(fc_column->bv_values[INCLUSION_CONTAINS_EMPTY]))
		{
			fc_column->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(true);
			PG_RETURN_BOOL(true);
		}

		PG_RETURN_BOOL(false);
	}

	if (fc_new)
		PG_RETURN_BOOL(true);

	/* 检查新值是否已经包含。 */
	fc_finfo = fc_inclusion_get_procinfo(fc_bdesc, fc_attno, PROCNUM_CONTAINS);
	if (fc_finfo != NULL &&
		DatumGetBool(FunctionCall2Coll(fc_finfo, fc_colloid,
									   fc_column->bv_values[INCLUSION_UNION],
									   fc_newval)))
		PG_RETURN_BOOL(false);

	/*
	 * 检查新值是否可与现有并集合并。如果不可合并，则标记
	 * 该值为包含不可合并元素并退出。
	 *
	 * 注意：在这一点上，我们可以将值从并集中移除，因为
	 * 它不再被使用。然而，BRIN 框架不允许值不存在。
	 * 有一天改进。
	 */
	fc_finfo = fc_inclusion_get_procinfo(fc_bdesc, fc_attno, PROCNUM_MERGEABLE);
	if (fc_finfo != NULL &&
		!DatumGetBool(FunctionCall2Coll(fc_finfo, fc_colloid,
										fc_column->bv_values[INCLUSION_UNION],
										fc_newval)))
	{
		fc_column->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true);
		PG_RETURN_BOOL(true);
	}

	/* 最后，将新值合并到现有的并集中。 */
	fc_finfo = fc_inclusion_get_procinfo(fc_bdesc, fc_attno, PROCNUM_MERGE);
	Assert(fc_finfo != NULL);
	fc_result = FunctionCall2Coll(fc_finfo, fc_colloid,
							   fc_column->bv_values[INCLUSION_UNION], fc_newval);
	if (!fc_attr->attbyval &&
		DatumGetPointer(fc_result) != DatumGetPointer(fc_column->bv_values[INCLUSION_UNION]))
	{
		pfree(DatumGetPointer(fc_column->bv_values[INCLUSION_UNION]));

		if (fc_result == fc_newval)
			fc_result = datumCopy(fc_result, fc_attr->attbyval, fc_attr->attlen);
	}
	fc_column->bv_values[INCLUSION_UNION] = fc_result;

	PG_RETURN_BOOL(true);
}

/*
 * BRIN 包含一致性函数
 *
 * 我们不再在一致性函数中处理 NULL 键，这现在由 AM 代码处理。 这意味着我们也不应该获得任何全 NULL 范围，因为这些不能与常规（非 [IS] NULL）键一致。
 *
 * 所有策略都是可选的。
 */
Datum brin_inclusion_consistent(PG_FUNCTION_ARGS)
{
	BrinDesc   *fc_bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
	BrinValues *fc_column = (BrinValues *) PG_GETARG_POINTER(1);
	ScanKey		fc_key = (ScanKey) PG_GETARG_POINTER(2);
	Oid			fc_colloid = PG_GET_COLLATION(),
				fc_subtype;
	Datum		fc_unionval;
	AttrNumber	fc_attno;
	Datum		fc_query;
	FmgrInfo   *fc_finfo;
	Datum		fc_result;

	/* 此操作类使用仅有三个参数的旧签名。 */
	Assert(PG_NARGS() == 3);

	/* 不应处理全 NULL 范围。 */
	Assert(!fc_column->bv_allnulls);

	/* 必须检查是否包含不可合并的元素。 */
	if (DatumGetBool(fc_column->bv_values[INCLUSION_UNMERGEABLE]))
		PG_RETURN_BOOL(true);

	fc_attno = fc_key->sk_attno;
	fc_subtype = fc_key->sk_subtype;
	fc_query = fc_key->sk_argument;
	fc_unionval = fc_column->bv_values[INCLUSION_UNION];
	switch (fc_key->sk_strategy)
	{
			/*
			 * 布局策略
			 *
			 * 这些通过逻辑上否定对立布局运算符的结果来实现；为了使其有效，对立运算符必须是操作类的一部分。 如果缺少所需策略，inclusion_get_strategy_procinfo() 将抛出错误。
			 *
			 * 如果任一参数为空，这些都将返回 false，因此无需检查空元素。
			 */

		case RTLeftStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTOverRightStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		case RTOverLeftStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTRightStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		case RTOverRightStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTLeftStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		case RTRightStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTOverLeftStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		case RTBelowStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTOverAboveStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		case RTOverBelowStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTAboveStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		case RTOverAboveStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTBelowStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		case RTAboveStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTOverBelowStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

			/*
			 * 重叠和包含策略
			 *
			 * 这些策略简单到我们可以简单地调用运算符并返回其结果。 空元素不会改变结果。
			 */

		case RTOverlapStrategyNumber:
		case RTContainsStrategyNumber:
		case RTContainsElemStrategyNumber:
		case RTSubStrategyNumber:
		case RTSubEqualStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													fc_key->sk_strategy);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_DATUM(fc_result);

			/*
			 * 被包含策略
			 *
			 * 我们不能仅仅调用原始运算符来处理被包含策略，因为某些元素可以被包含，即使并集不是；相反，我们使用重叠运算符。
			 *
			 * 我们单独检查空元素，因为它们不并入并集而被所有元素包含。
			 */

		case RTContainedByStrategyNumber:
		case RTSuperStrategyNumber:
		case RTSuperEqualStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTOverlapStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			if (DatumGetBool(fc_result))
				PG_RETURN_BOOL(true);

			PG_RETURN_DATUM(fc_column->bv_values[INCLUSION_CONTAINS_EMPTY]);

			/*
			 * 相邻策略
			 *
			 * 我们首先测试重叠，但为了安全起见，我们还需要调用实际的相邻运算符。
			 *
			 * 空元素不能与任何其他元素相邻，因此无需检查它。
			 */

		case RTAdjacentStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTOverlapStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			if (DatumGetBool(fc_result))
				PG_RETURN_BOOL(true);

			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTAdjacentStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_DATUM(fc_result);

			/*
			 * 基本比较策略
			 *
			 * 使用包含运算符支持相等策略是直接的。 通常，不等策略对于将与包含 BRIN 家族的操作类一起使用的类型没有太大意义，但可以通过逻辑上否定左侧和右侧运算符进行实现。
			 *
			 * 注意：这些策略不能与使用面积比较的几何数据类型一起使用！ 唯一的例外是“相同”策略。
			 *
			 * 空元素被认为小于其他元素。 我们不能使用空支持函数检查查询是否为空元素，因为查询可能与空支持函数参数的类型不同。 所以如果有可能空元素会改变结果，我们将返回 true。
			 */

		case RTLessStrategyNumber:
		case RTLessEqualStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTRightStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			if (!DatumGetBool(fc_result))
				PG_RETURN_BOOL(true);

			PG_RETURN_DATUM(fc_column->bv_values[INCLUSION_CONTAINS_EMPTY]);

		case RTSameStrategyNumber:
		case RTEqualStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTContainsStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			if (DatumGetBool(fc_result))
				PG_RETURN_BOOL(true);

			PG_RETURN_DATUM(fc_column->bv_values[INCLUSION_CONTAINS_EMPTY]);

		case RTGreaterEqualStrategyNumber:
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTLeftStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			if (!DatumGetBool(fc_result))
				PG_RETURN_BOOL(true);

			PG_RETURN_DATUM(fc_column->bv_values[INCLUSION_CONTAINS_EMPTY]);

		case RTGreaterStrategyNumber:
			/* 不需要检查空元素 */
			fc_finfo = fc_inclusion_get_strategy_procinfo(fc_bdesc, fc_attno, fc_subtype,
													RTLeftStrategyNumber);
			fc_result = FunctionCall2Coll(fc_finfo, fc_colloid, fc_unionval, fc_query);
			PG_RETURN_BOOL(!DatumGetBool(fc_result));

		default:
			/* 不应该发生 */
			elog(ERROR, "invalid strategy number %d", fc_key->sk_strategy);
			PG_RETURN_BOOL(false);
	}
}

/*
 * BRIN 包含联合函数
 *
 * 给定两个 BrinValues，将第一个更新为包含两个中的摘要值的联合。第二个保持不变。
 */
Datum brin_inclusion_union(PG_FUNCTION_ARGS)
{
	BrinDesc   *fc_bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
	BrinValues *fc_col_a = (BrinValues *) PG_GETARG_POINTER(1);
	BrinValues *fc_col_b = (BrinValues *) PG_GETARG_POINTER(2);
	Oid			fc_colloid = PG_GET_COLLATION();
	AttrNumber	fc_attno;
	Form_pg_attribute fc_attr;
	FmgrInfo   *fc_finfo;
	Datum		fc_result;

	Assert(fc_col_a->bv_attno == fc_col_b->bv_attno);
	Assert(!fc_col_a->bv_allnulls && !fc_col_b->bv_allnulls);

	fc_attno = fc_col_a->bv_attno;
	fc_attr = TupleDescAttr(fc_bdesc->bd_tupdesc, fc_attno - 1);

	/* 如果 B 包含空元素，必要时标记 A。 */
	if (!DatumGetBool(fc_col_a->bv_values[INCLUSION_CONTAINS_EMPTY]) &&
		DatumGetBool(fc_col_b->bv_values[INCLUSION_CONTAINS_EMPTY]))
		fc_col_a->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(true);

	/* 检查 A 是否包含不可合并的元素。 */
	if (DatumGetBool(fc_col_a->bv_values[INCLUSION_UNMERGEABLE]))
		PG_RETURN_VOID();

	/* 如果 B 包含不可合并的元素，同样标记 A。 */
	if (DatumGetBool(fc_col_b->bv_values[INCLUSION_UNMERGEABLE]))
	{
		fc_col_a->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true);
		PG_RETURN_VOID();
	}

	/* 检查 A 和 B 是否可以合并；如果不能，标记 A 为不可合并。 */
	fc_finfo = fc_inclusion_get_procinfo(fc_bdesc, fc_attno, PROCNUM_MERGEABLE);
	if (fc_finfo != NULL &&
		!DatumGetBool(FunctionCall2Coll(fc_finfo, fc_colloid,
										fc_col_a->bv_values[INCLUSION_UNION],
										fc_col_b->bv_values[INCLUSION_UNION])))
	{
		fc_col_a->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true);
		PG_RETURN_VOID();
	}

	/* 最后，将 B 合并到 A。 */
	fc_finfo = fc_inclusion_get_procinfo(fc_bdesc, fc_attno, PROCNUM_MERGE);
	Assert(fc_finfo != NULL);
	fc_result = FunctionCall2Coll(fc_finfo, fc_colloid,
							   fc_col_a->bv_values[INCLUSION_UNION],
							   fc_col_b->bv_values[INCLUSION_UNION]);
	if (!fc_attr->attbyval &&
		DatumGetPointer(fc_result) != DatumGetPointer(fc_col_a->bv_values[INCLUSION_UNION]))
	{
		pfree(DatumGetPointer(fc_col_a->bv_values[INCLUSION_UNION]));

		if (fc_result == fc_col_b->bv_values[INCLUSION_UNION])
			fc_result = datumCopy(fc_result, fc_attr->attbyval, fc_attr->attlen);
	}
	fc_col_a->bv_values[INCLUSION_UNION] = fc_result;

	PG_RETURN_VOID();
}

/*
 * 缓存并返回包含 opclass 的支持过程
 *
 * 返回对应于给定函数支持编号的过程，如果不存在则返回 null。
 */
static FmgrInfo * fc_inclusion_get_procinfo(BrinDesc *fc_bdesc, uint16 fc_attno, uint16 fc_procnum)
{
	InclusionOpaque *fc_opaque;
	uint16		fc_basenum = fc_procnum - PROCNUM_BASE;

	/*
	 * 我们将其缓存于不透明结构中，以避免重复的系统缓存查找。
	 */
	fc_opaque = (InclusionOpaque *) fc_bdesc->bd_info[fc_attno - 1]->oi_opaque;

	/*
	 * 如果我们已经搜索过这个过程且未找到它，就不必再搜索了。
	 */
	if (fc_opaque->extra_proc_missing[fc_basenum])
		return NULL;

	if (fc_opaque->extra_procinfos[fc_basenum].fn_oid == InvalidOid)
	{
		if (RegProcedureIsValid(index_getprocid(fc_bdesc->bd_index, fc_attno,
												fc_procnum)))
		{
			fmgr_info_copy(&fc_opaque->extra_procinfos[fc_basenum],
						   index_getprocinfo(fc_bdesc->bd_index, fc_attno, fc_procnum),
						   fc_bdesc->bd_context);
		}
		else
		{
			fc_opaque->extra_proc_missing[fc_basenum] = true;
			return NULL;
		}
	}

	return &fc_opaque->extra_procinfos[fc_basenum];
}

/*
 * 缓存并返回给定策略的过程
 *
 * 返回对应于给定子类型和策略编号的过程。索引的数据类型将用作运算符的左侧，而给定的子类型将用作右侧。
 * 如果 pg_amop 行不存在，则引发错误，但在正确配置的 opclass 中不应发生这种情况。
 *
 * 当 opclass 的数据类型与列或表达式的数据类型不同时时，总是会引发错误。这发生在列数据类型隐式转换为 opclass 数据类型时。我们不处理类型转换，因为通过将存储数据类型设置为 opclass 的数据类型，可以很容易地避免这种情况。相同的问题不适用于右侧的数据类型，因为 ScanKey 中的类型总是与 opclass 的类型匹配。
 *
 * 注意：此函数镜像 minmax_get_strategy_procinfo；如果在这里进行更改，请参阅该函数。
 */
static FmgrInfo * fc_inclusion_get_strategy_procinfo(BrinDesc *fc_bdesc, uint16 fc_attno, Oid fc_subtype,
								uint16 fc_strategynum)
{
	InclusionOpaque *fc_opaque;

	Assert(fc_strategynum >= 1 &&
		   fc_strategynum <= RTMaxStrategyNumber);

	fc_opaque = (InclusionOpaque *) fc_bdesc->bd_info[fc_attno - 1]->oi_opaque;

	/*
	 * 我们在不透明结构中缓存最后一个子类型的过程，以避免重复的系统缓存查询。如果子类型更改，
	 * 则使所有缓存条目失效。
	 */
	if (fc_opaque->cached_subtype != fc_subtype)
	{
		uint16		fc_i;

		for (fc_i = 1; fc_i <= RTMaxStrategyNumber; fc_i++)
			fc_opaque->strategy_procinfos[fc_i - 1].fn_oid = InvalidOid;
		fc_opaque->cached_subtype = fc_subtype;
	}

	if (fc_opaque->strategy_procinfos[fc_strategynum - 1].fn_oid == InvalidOid)
	{
		Form_pg_attribute fc_attr;
		HeapTuple	fc_tuple;
		Oid			fc_opfamily,
					fc_oprid;
		bool		fc_isNull;

		fc_opfamily = fc_bdesc->bd_index->rd_opfamily[fc_attno - 1];
		fc_attr = TupleDescAttr(fc_bdesc->bd_tupdesc, fc_attno - 1);
		fc_tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(fc_opfamily),
								ObjectIdGetDatum(fc_attr->atttypid),
								ObjectIdGetDatum(fc_subtype),
								Int16GetDatum(fc_strategynum));

		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
				 fc_strategynum, fc_attr->atttypid, fc_subtype, fc_opfamily);

		fc_oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, fc_tuple,
												 Anum_pg_amop_amopopr, &fc_isNull));
		ReleaseSysCache(fc_tuple);
		Assert(!fc_isNull && RegProcedureIsValid(fc_oprid));

		fmgr_info_cxt(get_opcode(fc_oprid),
					  &fc_opaque->strategy_procinfos[fc_strategynum - 1],
					  fc_bdesc->bd_context);
	}

	return &fc_opaque->strategy_procinfos[fc_strategynum - 1];
}
